import { _decorator, Animation, AudioClip, AudioSource, Color, Component, Constraint, instantiate, Node, Prefab, Rect, resources, Sprite, Vec3 } from 'cc';
import { BulletManager } from './manager/BulletManager';
import { myGame } from './MyGame';
import { Bullet } from './Bullet';
import { Map_Layer } from './Map_Layer';
import { Tank } from './tank/Tank';
import { BlockType, Camp } from './Define';
import { TankManager } from './manager/TankManager';
import { myUtils } from './utils/MyUtils';
import { AUDIO_RES_PATH, BLOCK_SIZE, BULLET_SIZE, TANK_RES_PATH, TANK_SIZE } from './Const';
import { LayerManager } from './manager/LayerManager';
import { UI_Layer } from './UI_Layer';
const { ccclass, property } = _decorator;

@ccclass('Game_Layer')
export class Game_Layer extends Component {
    @property(Node)
    public bullets: Node = null

    @property(Map_Layer)
    public mapLayer: Map_Layer = null

    @property(Node)
    public tanks: Node = null

    @property(Prefab)
    public effectTankBoom: Prefab = null

    @property(Prefab)
    public effectBulletBoom: Prefab = null

    @property(LayerManager)
    public layerManager: LayerManager = null

    @property(UI_Layer)
    public uiLayer: UI_Layer = null

    onLoad() {
        myGame.bulletManager = this.bullets.getComponent(BulletManager)
        myGame.tankManager = this.tanks.getComponent(TankManager)
    }

    onEnable() {
        resources.loadDir(TANK_RES_PATH, (err, data) => {
            if (err) {
                console.error(err)
                return
            }

            myGame.tankManager.spawnEnemies()
            myGame.tankManager.spawnPlayer()
            this.uiLayer.updateEnemyNum()
            this.uiLayer.updatePlayerLife()
        })

        resources.loadDir(AUDIO_RES_PATH, AudioClip, (err, data: AudioClip[]) => {
            if (err) {
                console.error(err)
                return
            }

            this.playAudio('game_start')
        })

        this.uiLayer.updateLevel()
    }

    onDisable() {
        const children = this.bullets.children
        for (let i = children.length - 1; i >= 0; i--) {
            myGame.bulletManager.pushBullet(children[i])
        }
    }

    update(deltaTime: number) {
        if (myGame.isGameOver)
            return

        let tempRect = new Rect()
        const tankCmps = this.tanks.getComponentsInChildren(Tank)
        tankCmps.forEach(cmp => {
            cmp.frameMove(deltaTime)
            if (cmp.curSpeed == 0)
                return

            const dest = cmp.getDesireDestination(deltaTime)
            this.mapLayer.tankBoundaryCorrect(dest)
            tempRect = this.mapLayer.convertTankBoundingBoxToMap(dest, tempRect)
            this.mapLayer.checkBlockCollision(tempRect, cmp.direction, (type: BlockType, block: Node, col: number, row: number) => {
                if (type == BlockType.Stone || type == BlockType.Wall || type == BlockType.River) {
                    // block.getComponent(Sprite).color = Color.RED
                    this.mapLayer.adjustTankPos(dest, cmp.direction, block)
                    return true
                }
                return false
            })
            cmp.node.setPosition(dest.x, dest.y)
        })

        let respawnTank = false
        let tempTankRect = new Rect()
        const tankCmpsCopy = tankCmps.slice()
        const reuseBullet: Node[] = []
        const reuseTank: Node[] = []
        const bulletCmps = this.bullets.getComponentsInChildren(Bullet)
        bulletCmps.forEach(cmp => {
            cmp.move(deltaTime)
            if (this.mapLayer.isBulletOutOfBoundary(cmp.node.position)) {
                myGame.bulletManager.pushBullet(cmp.node)
            }
            else {
                tempRect = myUtils.getBoundingBox(cmp.node.position, BULLET_SIZE, tempRect)
                for (let i = 0; i < tankCmpsCopy.length; i++) {
                    const tankCmp = tankCmpsCopy[i]
                    if (tankCmp.getCamp() == cmp.camp)
                        continue
                    
                    const tank = tankCmp.node
                    tempTankRect = myUtils.getBoundingBox(tank.position, TANK_SIZE, tempTankRect)
                    if (tempRect.intersects(tempTankRect)) {
                        reuseBullet.push(cmp.node)
                        if (!tankCmp.isInvincible()) {
                            tankCmp.die()
                            if (tankCmp != myGame.player) {
                                reuseTank.push(tankCmp.node)
                                tankCmpsCopy.splice(i, 1)
                                respawnTank = true
                                myGame.enemyDieNum++
                            }
                        }
                        return
                    }
                }

                //子弹和子弹的碰撞
                let tempOtherBulletRect = new Rect()
                for (let i = 0; i < bulletCmps.length; i++) {
                    const bulletCmp = bulletCmps[i]
                    if (bulletCmp.camp == cmp.camp || bulletCmp == cmp)
                        continue

                    if (reuseBullet.indexOf(bulletCmp.node) != -1)
                        continue

                    tempOtherBulletRect = myUtils.getBoundingBox(bulletCmp.node.position, BULLET_SIZE, tempOtherBulletRect)
                    if (tempRect.intersects(tempOtherBulletRect)) {
                        reuseBullet.push(cmp.node)
                        reuseBullet.push(bulletCmp.node)
                        return
                    }
                }

                tempRect = this.mapLayer.convertBulletBoundingBoxToMap(cmp.node.position, tempRect)
                this.mapLayer.checkBlockCollision(tempRect, cmp.direction,(type: BlockType, block: Node, col: number, row: number) => {
                    if (type == BlockType.Stone) {
                        reuseBullet.push(cmp.node)
                        return true
                    }
                    else if (type == BlockType.Wall) {
                        reuseBullet.push(cmp.node)
                        this.mapLayer.destroyBlock(col, row)
                        return true
                    }
                    else if (type == BlockType.Base) {
                        reuseBullet.push(cmp.node)
                        this.mapLayer.BaseDestroy()
                        myGame.isGameOver = true
                        return true
                    }
                    return false
                })
            }
        })

        if (myGame.player?.isDie) {
            if (myGame.player.hasLife()) {
                myGame.tankManager.respawnPlayer()
            }
            else {
                myGame.isGameOver = true
            }
            this.uiLayer.updatePlayerLife()
        }

        reuseBullet.forEach(bullet => {
            this.playBulletBoom(bullet.position)
            myGame.bulletManager.pushBullet(bullet)
        })

        reuseTank.forEach(tank => {
            this.playTankBoom(tank.position)
            myGame.tankManager.pushTank(tank)
        })

        if (respawnTank) {
            if (myGame.checkIsWin()) {
                this.nextLevel()
            }
            else {
                myGame.tankManager.spawnEnemies()
            }
            this.uiLayer.updateEnemyNum()
        }

        if (myGame.isGameOver) {
            this.gameOver()
        }
    }

    playTankBoom(pos: Vec3) {
        const effect = instantiate(this.effectTankBoom)
        effect.parent = this.node
        effect.position = pos
        effect.getComponent(Animation).once(Animation.EventType.FINISHED, () => {
            effect.destroy()
        })
    }

    playBulletBoom(pos: Vec3) {
        const effect = instantiate(this.effectBulletBoom)
        effect.parent = this.node
        effect.position = pos
        effect.getComponent(Animation).once(Animation.EventType.FINISHED, () => {
            effect.destroy()
        })
    }

    playAudio(name: string) {
        const clip = resources.get<AudioClip>(AUDIO_RES_PATH + name)
        if (!clip){ 
            console.error('音频不存在:', name)
            return
        }

        const audioSource = this.node.getComponent(AudioSource)
        audioSource.stop()
        audioSource.clip = clip
        audioSource.play()
    }

    gameOver() {
        this.playAudio('game_over')
        this.layerManager.gameOver()
    }

    nextLevel() {
        myGame.nextLevel()
        this.layerManager.nextLevel()
    }
}


